--[[ scheme_type: generic author: Alundaio modified_by: --]] -------------------- evaid = 90005 actid = 90005 -------------------- -------------------- local level = level local alife = alife -------------------- local Items local Enable local StateWalk local StateRun local RunDist local MaxDetectDistance local UseCorpseLootTable local LootTable ------------------------------------------ -- Localized Functions ------------------------------------------ local function npc_on_item_take(npc,item) if not (npc:alive()) then return end --utils_data.debug_write(strformat("xr_gather_items:npc_on_item_take %s item=%s",npc:name(),item:name())) Items[item:id()] = nil --[[ -- Create one bullet, because NPCs need one at least to use the weapon properly if IsWeapon(item) and (not IsItem("fake_ammo_wpn",item:section())) then local ammo_list = utils_item.get_ammo(item:section(), item:id()) alife_create_item( ammo_list[math.random(1,2)], npc, { ammo = 1 } ) --printf("- NPC on item take | created one bullet [%s] for [%s]", ammo_list[2], item:section()) end --]] -- skip story npcs except companions if not (npc:has_info("npcx_is_companion")) then if (get_object_story_id(npc:id())) then return end end -- ATTACH WEAPON ADDONS local wpn = npc:best_weapon() if (wpn and item) then wpn:weapon_addon_attach(item) end -- CHANGE VISUAL FOR OUTFIT -- TODO: FIX IN ENGINE --[[ if not (IsOutfit(item)) then return end local story_name = get_object_story_id(npc:id()) if (story_name ~= nil) then return end local npc_visual = ini_sys:r_string_ex(item:section(),"npc_visual") if not (npc_visual) then return end npc:set_visual_name(npc_visual) --]] end local function actor_on_item_take(item) if (Items and item) then Items[item:id()] = nil end end local function get_best_detector(npc) local detectors = { "detector_simple", "detector_advanced", "detector_elite", "detector_scientific" } for i=1, #detectors do local obj = npc:object(detectors[i]) if obj ~= nil then return obj end end end local function get_rand_vertex(npc,vo) local dir = npc:position():sub(vo:position()) dir = vector_rotate_y(dir,math.random(5,45)) return vo:vertex_in_direction(vo:level_vertex_id(),dir,1) end --------------------------------------- -- Register Callbacks --------------------------------------- function on_game_start() local ini = ini_file("ai_tweaks\\xr_gather_items.ltx") Enable = ini:r_bool_ex("settings","enable",false) if not (Enable) then return end Items = {} StateWalk = ini:r_string_ex("settings","state_walk") or "patrol" StateRun = ini:r_string_ex("settings","state_run") or "rush" RunDist = ini:r_float_ex("settings","run_dist") or 3 MaxDetectDistance = ini:r_float_ex("settings","max_detect_dist") or 900 ArtifactDectection = ini:r_bool_ex("settings","allow_artifact_detection",false) ArtifactSearchTimer = ini:r_float_ex("settings","artifact_search_time") or 60000 ArtifactCommunities = utils_data.collect_section(ini,"artifact_communities",true) UseCorpseLootTable = ini:r_bool_ex("settings","use_xr_corpse_detection_loot_table",false) if not (UseCorpseLootTable) then LootTable = utils_data.collect_section(ini,"lootable",true) end RegisterScriptCallback("npc_on_item_take",npc_on_item_take) RegisterScriptCallback("actor_on_item_take",actor_on_item_take) end function gather_item(npc) if not (db.actor) then return end local id = npc:id() local st = id and db.storage[id] and db.storage[id].gather_items -- this should never happen if not (st) then printf("wtf no db.storage[%s].gather_items",id) return end local selected_id = st.selected_id --printf("selected_id = %s",selected_id) -- reset all scheme dependent variables if (st.selected_id) then Items[st.selected_id] = nil st.selected_id = nil end st.index = 1 st.nearest_dist = nil st.nearest_vid = nil st.nearest_pos = nil st.nearest_id = nil st.stage = 0 st.state = nil st.vid = nil st.vid_pos = nil local itm = selected_id and selected_id ~= 0 and selected_id ~=65535 and level.object_by_id(selected_id) local loot_table = UseCorpseLootTable ~= true and LootTable or xr_corpse_detection.lootable_table if (itm == nil or loot_table[itm:section()] == nil) then --printf("nooooo! itm=%s lootable=%s",itm and itm:name(), itm and loot_table[itm:section()]) return end local se_itm = alife_object(selected_id) if not (se_itm) then return end if IsArtefact(nil,se_itm:clsid()) then local anomal_zone = bind_anomaly_zone.parent_zones_by_artefact_id[selected_id] if (anomal_zone) then anomal_zone:on_artefact_take(itm) else bind_anomaly_zone.artefact_ways_by_id[selected_id] = nil end local artefact = itm:get_artefact() artefact:FollowByPath("NULL",0,vector():set(500,500,500)) if (dynamic_news_manager and ui_options.get("alife/dynamic_news/found_artifact_news")) then dynamic_news_manager.get_dynamic_news():FoundArtefact(npc,itm) end -- increase NPC rank for stalkering game_statistics.increment_npc_statistic(npc,"artefacts_found") elseif IsWeapon(itm) then alife_clone_weapon(se_itm, nil, id) return end alife_create_item(itm:section(), npc) alife_release(se_itm) end ------------------------------------------------------ -- eva_gather_itm ------------------------------------------------------ class "eva_gather_itm" (property_evaluator) function eva_gather_itm:__init(name, storage, npc) super (nil, name) self.st = storage self.st.stage = 0 end function near_actor(obj) local dist = tonumber(ui_options.get("gameplay/general/npc_loot_distance")) if dist and dist > 0 then return db.actor:position():distance_to(obj:position()) < dist else return false end end function eva_gather_itm:find_valid_item() if (self.st.cstackprevent) then printf("C Stack Overflow Prevention: warning xr_gather_items scheme making repeated calls without return; save now and reload!") return false end if (self.st.selected_id) then local se_itm = self.st.selected_id ~= 0 and self.st.selected_id ~= 65535 and alife_object(self.st.selected_id) if (se_itm and se_itm.parent_id == 65535) then local itm = level.object_by_id(se_itm.id) if (itm and not near_actor(itm)) then return true end end Items[self.st.selected_id] = nil self.st.selected_id = nil end local tg = time_global() self.st.__dtimer = not self.st.__dtimer and tg + 250 or self.st.__dtimer if (tg < self.st.__dtimer) then return false end self.st.__dtimer = nil local loot_table = UseCorpseLootTable ~= true and LootTable or xr_corpse_detection.lootable_table if not (loot_table) then return false end if not (self.st.index) then self.st.index = 1 end if not (self.st.memory) then self.st.memory = {} end local npc = self.object local size = #self.st.memory if (size == 0) then local obj_id self.st.cstackprevent = true for o in npc:memory_visible_objects() do local obj = o and o:object() if (obj) then obj_id = obj:id() if (loot_table[obj:section()] ~= nil and not near_actor(obj)) then size = size + 1 table.insert(self.st.memory,obj_id) end end end self.st.cstackprevent = nil if (size == 0) then return false end end if (size == 0 or self.st.index > size) then if (self.st.nearest_id and Items[self.st.nearest_id] == nil and (not game_setup.is_world_item(self.st.nearest_id))) then if (self.st.selected_id and Items[self.st.selected_id]) then Items[self.st.selected_id] = nil end self.st.selected_id = self.st.nearest_id self.st.vid = self.st.nearest_vid self.st.vid_pos = self.st.nearest_pos Items[self.st.selected_id] = npc:id() self.st.stage = 1 self.st.nearest_dist = nil self.st.nearest_vid = nil self.st.nearest_pos = nil self.st.nearest_id = nil self.st.index = 1 local be = npc:best_enemy() local dist = npc:position():distance_to(self.st.vid_pos) if (be or dist < RunDist) then self.st.state = StateWalk else self.st.state = StateRun end return true end self.st.index = 1 self.st.nearest_dist = nil self.st.nearest_vid = nil self.st.nearest_pos = nil self.st.nearest_id = nil empty_table(self.st.memory) return false end local itm_id = self.st.memory[self.st.index] self.st.index = self.st.index + 1 local itm = itm_id and itm_id ~= 0 and itm_id ~= 65535 and level.object_by_id(itm_id) local se_itm = itm and loot_table[itm:section()] and Items[itm_id] == nil and alife_object(itm_id) if not (se_itm and se_itm.parent_id == 65535) then return false end if (get_object_story_id(itm_id)) then return false end if (npc:best_enemy()) then if not (IsWeapon(itm)) then return false end if (IsWeapon(npc:active_item())) then return false end end if (IsArtefact(nil,se_itm:clsid())) then if not (ArtifactDectection) then return false end if (xr_logic.pick_section_from_condlist(db.actor, npc, self.st.gather_artefact_items_enabled) == "false") then return false end local squad = get_object_squad(npc) local squad_community = squad and squad.player_id or character_community(npc) if not (ArtifactCommunities[squad_community]) then return false end else if not (npc:see(itm)) then return false end end local itm_pos = itm:position() local dist = npc:position():distance_to_sqr(itm_pos) if (self.st.nearest_dist == nil or dist < self.st.nearest_dist) and (dist < MaxDetectDistance) then local vid = level.vertex_id(itm_pos) if (level.vertex_position(vid):distance_to_sqr(itm_pos) > 16) then vid = itm:level_vertex_id() end if (npc:accessible(vid) and level.vertex_position(vid):distance_to_sqr(itm_pos) <= 15) then self.st.nearest_dist = dist self.st.nearest_vid = vid self.st.nearest_pos = itm_pos self.st.nearest_id = itm_id --printf("item = %s id=%s",itm:section(),itm_id) end end return false end function eva_gather_itm:evaluate() --utils_data.debug_write("eva_gather_itm") local npc = self.object if (npc:alive() ~= true or IsWounded(npc)) then return false end if (npc:best_enemy() and IsWeapon(npc:active_item())) then return false end local st = db.storage[npc:id()] if (st) and (st.active_scheme == "camper" or st.help_wounded and st.help_wounded.selected_id ~= nil) then return false end if (xr_danger.has_danger(npc)) then return false end if (xr_conditions.surge_started() or xr_logic.pick_section_from_condlist(db.actor, self.object, self.st.gather_items_enabled) == "false") then return false end if utils_item.is_overweight(npc) then return false end if (self:find_valid_item()) then return true end return false end ------------------------------------------------------ -- act_gather_itm ------------------------------------------------------ class "act_gather_itm" ( action_base ) function act_gather_itm:__init( name, storage ) super ( nil, name ) self.st = storage end function act_gather_itm:initialize() local npc = self.object npc:set_desired_position() npc:set_desired_direction() npc:set_path_type(game_object.level_path) --npc:set_dest_level_vertex_id(self.st.vid) state_mgr.set_state(npc,self.st.state,nil,nil,nil,{fast_set = true}) self.st.vid = utils_obj.send_to_nearest_accessible_vertex(npc,self.st.vid,"gather_items") action_base.initialize(self) end local function execute_gather_item(npc,self) if (state_mgr.get_state(npc) == "pickup_crouch") then self.st.__stimer = not self.st.__stimer and time_global() + 15000 or self.st.__stimer if (time_global() > self.st.__stimer) then self.st.__stimer = nil gather_item(npc) end return end if not (self.st.selected_id) then return end local se_itm = self.st.selected_id ~= 65535 and self.st.selected_id ~= 0 and alife_object(self.st.selected_id) local dist = npc:position():distance_to(self.st.vid_pos) if (se_itm and IsArtefact(nil,se_itm:clsid())) then local tg = time_global() if not (self.st.artifact_timer) then self.st.artifact_timer = tg + ArtifactSearchTimer end if (tg > self.st.artifact_timer) then state_mgr.set_state(npc, "pickup_crouch") self.st.stage = 2 return end if (self.st.stage == 1) then local state if (dist < 10) then state = random_choice("probe_crouch") else state = random_choice("search","probe_stand","probe_crouch") end state_mgr.set_state(npc,state,nil,nil, {look_position = se_itm.position} ) npc:set_movement_type(move.stand) self.st.stage = 3 elseif (self.st.stage == 3) then if not (self.st.timer) then self.st.timer = tg + math.random(5000,8000) end if (tg > self.st.timer) then state_mgr.set_state(npc,"walk") self.st.timer = tg + math.random(15000,20000) self.st.stage = 4 end elseif (self.st.stage == 4) then if not (self.st.timer) then self.st.timer = tg + math.random(15000,20000) end if (tg > self.st.timer) then self.st.stage = 1 self.st.timer = nil end end if (npc:level_vertex_id() == self.st.vid) then state_mgr.set_state(npc, "pickup_crouch") else self.st.vid = utils_obj.send_to_nearest_accessible_vertex(npc, self.st.vid,"gather_items") end return end if (npc:level_vertex_id() == self.st.vid) then state_mgr.set_state(npc, "pickup_crouch") else if (npc:path_type() ~= game_object.level_path) then npc:set_path_type(game_object.level_path) end state_mgr.set_state(npc,self.st.state,nil, nil, {look_position = self.st.nearest_pos}) self.st.vid = utils_obj.send_to_nearest_accessible_vertex(npc, self.st.vid,"gather_items") end end function act_gather_itm:execute() action_base.execute(self) execute_gather_item(self.object,self) end function act_gather_itm:finalize() --state_mgr.set_state(self.object, "idle") local st = self.st if (st.selected_id) then Items[st.selected_id] = nil st.selected_id = nil end st.index = 1 st.nearest_dist = nil st.nearest_vid = nil st.nearest_pos = nil st.nearest_id = nil st.stage = 0 st.vid = nil st.vid_pos = nil action_base.finalize(self) end ---------------------------------------------------------------------------------------------------------------------- -- Default EVALUATOR ---------------------------------------------------------------------------------------------------------------------- class "evaluator_gather_items" (property_evaluator) function evaluator_gather_items:__init(name, storage, npc) super (nil, name) self.st = storage end function evaluator_gather_items:evaluate() if true then return false end local enabled = xr_logic.pick_section_from_condlist(db.actor, self.object, self.st.gather_items_enabled) if (enabled ~= "true") then return false end if (xr_conditions.surge_started() == true) then return false end local items_to_pickup = self.object:is_there_items_to_pickup() return items_to_pickup end ---------------------------------------------------------------------------------------------------------------------- -- BINDER ---------------------------------------------------------------------------------------------------------------------- function setup_generic_scheme(npc,ini,scheme,section,stype,temp) local st = xr_logic.assign_storage_and_bind(npc, ini,"gather_items","gather_items",temp) end function add_to_binder(npc,ini,scheme,section,st,temp) if not npc then return end local manager = npc:motivation_action_manager() if not manager then return end local wp = world_property local evaluator1 = evaluator_gather_items("gather_items",st,npc) if not evaluator1 then return end manager:remove_evaluator(stalker_ids.property_items) manager:add_evaluator(stalker_ids.property_items,evaluator1) --manager:add_evaluator(stalker_ids.property_items, property_evaluator_const(false)) if (character_community(npc) == "zombied" or npc:section() == "actor_visual_stalker") then manager:add_evaluator(evaid,property_evaluator_const(false)) temp.needs_configured = false return end local evaluator2 = eva_gather_itm("eva_gather_itm",st,npc) temp.action = act_gather_itm("act_gather_itm",st) if not evaluator2 or not temp.action then return end manager:add_evaluator(evaid,evaluator2) temp.action:add_precondition(wp(stalker_ids.property_alive,true)) temp.action:add_precondition(wp(stalker_ids.property_enemy, false)) temp.action:add_precondition(wp(stalker_ids.property_danger,false)) temp.action:add_precondition(wp(evaid,true)) temp.action:add_effect(wp(evaid, false)) manager:add_action(actid, temp.action) end function configure_actions(npc,ini,scheme,section,stype,temp) if not npc then return end local manager = npc:motivation_action_manager() if not manager or not temp.action then return end local wp = world_property temp.action:add_precondition( wp(xr_evaluators_id.wounded_exist,false) ) temp.action:add_precondition( wp(xr_evaluators_id.stohe_meet_base + 1, false) ) temp.action:add_precondition( wp(xr_evaluators_id.sidor_wounded_base, false) ) temp.action:add_precondition( wp(xr_evaluators_id.abuse_base, false) ) local action local p = { stalker_ids.action_combat_planner, xr_actions_id.state_mgr + 2, xr_actions_id.alife } for i=1,#p do --printf("ACTION_ALIFE_ID(xr_gather_items.configure_actions): " .. tostring(p[i])) action = manager:action(p[i]) if (action) then action:add_precondition( wp(evaid,false) ) else printf("xr_gather_items: no action id p[%s]",i) end end end function reset_generic_scheme(npc,scheme,section,stype,st) if not (st.gather_items) then printf("st.gather_items is nil! %s",npc and npc:name()) return end if (st.gather_items.selected_id and Items[st.gather_items.selected_id]) then Items[st.gather_items.selected_id] = nil end st.gather_items.selected_id = nil st.gather_items.vid = nil st.gather_items.vid_pos = nil st.gather_items.gather_items_enabled = xr_logic.parse_condlist(npc, section, "gather_items_enabled", st.ini:r_string_ex(section,"gather_items_enabled") or "true") st.gather_items.gather_artefact_items_enabled = xr_logic.parse_condlist(npc, section, "gather_artefact_items_enabled",st.ini:r_string_ex(section,"gather_artefact_items_enabled") or "true") end function npc_add_precondition(action) if not action then return end action:add_precondition(world_property(evaid,false)) end function is_under_gather_items(npc) local mgr = npc:motivation_action_manager() if not mgr or not mgr:initialized() then return false end return mgr:current_action_id() == actid end